{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "whjsJasuhstV"
   },
   "source": [
    "<a href=\"https://colab.research.google.com/github/jeffheaton/app_generative_ai/blob/main/t81_559_class_02_4_software_eng.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "euOZxlIMhstX"
   },
   "source": [
    "# T81-559: Applications of Generative Artificial Intelligence\n",
    "**Module 2: Code Generation**\n",
    "* Instructor: [Jeff Heaton](https://sites.wustl.edu/jeffheaton/), McKelvey School of Engineering, [Washington University in St. Louis](https://engineering.wustl.edu/Programs/Pages/default.aspx)\n",
    "* For more information visit the [class website](https://sites.wustl.edu/jeffheaton/t81-558/)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "d4Yov72PhstY"
   },
   "source": [
    "# Module 2 Material\n",
    "\n",
    "* Part 2.1: Prompting for Code Generation [[Video]](https://www.youtube.com/watch?v=HVId6kYKKgQ) [[Notebook]](t81_559_class_02_1_dev.ipynb)\n",
    "* Part 2.2: Handling Revision Prompts [[Video]](https://www.youtube.com/watch?v=APpV46tplXA) [[Notebook]](t81_559_class_02_2_multi_prompt.ipynb)\n",
    "* Part 2.3: Using a LLM to Help Debug [[Video]](https://www.youtube.com/watch?v=VPqSNb38QK0) [[Notebook]](t81_559_class_02_3_llm_debug.ipynb)\n",
    "* **Part 2.4: Tracking Prompts in Software Development** [[Video]](https://www.youtube.com/watch?v=oUFUuYfvXZU) [[Notebook]](t81_559_class_02_4_software_eng.ipynb)\n",
    "* Part 2.5: Limits of LLM Code Generation [[Video]](https://www.youtube.com/watch?v=dKtRI0LZSyY) [[Notebook]](t81_559_class_02_5_code_gen_limits.ipynb)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "AcAUP0c3hstY"
   },
   "source": [
    "# Google CoLab Instructions\n",
    "\n",
    "The following code ensures that Google CoLab is running and maps Google Drive if needed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "xsI496h5hstZ",
    "outputId": "4c950075-215a-464b-f04b-7a831d498767"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Note: using Google CoLab\n",
      "Collecting langchain\n",
      "  Downloading langchain-0.1.16-py3-none-any.whl (817 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m817.7/817.7 kB\u001b[0m \u001b[31m3.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting langchain_openai\n",
      "  Downloading langchain_openai-0.1.4-py3-none-any.whl (33 kB)\n",
      "Requirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (6.0.1)\n",
      "Requirement already satisfied: SQLAlchemy<3,>=1.4 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.0.29)\n",
      "Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (3.9.5)\n",
      "Requirement already satisfied: async-timeout<5.0.0,>=4.0.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (4.0.3)\n",
      "Collecting dataclasses-json<0.7,>=0.5.7 (from langchain)\n",
      "  Downloading dataclasses_json-0.6.4-py3-none-any.whl (28 kB)\n",
      "Collecting jsonpatch<2.0,>=1.33 (from langchain)\n",
      "  Downloading jsonpatch-1.33-py2.py3-none-any.whl (12 kB)\n",
      "Collecting langchain-community<0.1,>=0.0.32 (from langchain)\n",
      "  Downloading langchain_community-0.0.34-py3-none-any.whl (1.9 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.9/1.9 MB\u001b[0m \u001b[31m10.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting langchain-core<0.2.0,>=0.1.42 (from langchain)\n",
      "  Downloading langchain_core-0.1.46-py3-none-any.whl (299 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m299.3/299.3 kB\u001b[0m \u001b[31m4.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting langchain-text-splitters<0.1,>=0.0.1 (from langchain)\n",
      "  Downloading langchain_text_splitters-0.0.1-py3-none-any.whl (21 kB)\n",
      "Collecting langsmith<0.2.0,>=0.1.17 (from langchain)\n",
      "  Downloading langsmith-0.1.51-py3-none-any.whl (115 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m116.0/116.0 kB\u001b[0m \u001b[31m4.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: numpy<2,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (1.25.2)\n",
      "Requirement already satisfied: pydantic<3,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.7.0)\n",
      "Requirement already satisfied: requests<3,>=2 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.31.0)\n",
      "Requirement already satisfied: tenacity<9.0.0,>=8.1.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (8.2.3)\n",
      "Collecting openai<2.0.0,>=1.10.0 (from langchain_openai)\n",
      "  Downloading openai-1.23.6-py3-none-any.whl (311 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m311.6/311.6 kB\u001b[0m \u001b[31m15.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting tiktoken<1,>=0.5.2 (from langchain_openai)\n",
      "  Downloading tiktoken-0.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.8 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.8/1.8 MB\u001b[0m \u001b[31m15.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.3.1)\n",
      "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (23.2.0)\n",
      "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.4.1)\n",
      "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (6.0.5)\n",
      "Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.9.4)\n",
      "Collecting marshmallow<4.0.0,>=3.18.0 (from dataclasses-json<0.7,>=0.5.7->langchain)\n",
      "  Downloading marshmallow-3.21.1-py3-none-any.whl (49 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.4/49.4 kB\u001b[0m \u001b[31m1.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting typing-inspect<1,>=0.4.0 (from dataclasses-json<0.7,>=0.5.7->langchain)\n",
      "  Downloading typing_inspect-0.9.0-py3-none-any.whl (8.8 kB)\n",
      "Collecting jsonpointer>=1.9 (from jsonpatch<2.0,>=1.33->langchain)\n",
      "  Downloading jsonpointer-2.4-py2.py3-none-any.whl (7.8 kB)\n",
      "Collecting packaging<24.0,>=23.2 (from langchain-core<0.2.0,>=0.1.42->langchain)\n",
      "  Downloading packaging-23.2-py3-none-any.whl (53 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m53.0/53.0 kB\u001b[0m \u001b[31m3.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting orjson<4.0.0,>=3.9.14 (from langsmith<0.2.0,>=0.1.17->langchain)\n",
      "  Downloading orjson-3.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (141 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m141.1/141.1 kB\u001b[0m \u001b[31m939.0 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.10.0->langchain_openai) (3.7.1)\n",
      "Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai<2.0.0,>=1.10.0->langchain_openai) (1.7.0)\n",
      "Collecting httpx<1,>=0.23.0 (from openai<2.0.0,>=1.10.0->langchain_openai)\n",
      "  Downloading httpx-0.27.0-py3-none-any.whl (75 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m75.6/75.6 kB\u001b[0m \u001b[31m5.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.10.0->langchain_openai) (1.3.1)\n",
      "Requirement already satisfied: tqdm>4 in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.10.0->langchain_openai) (4.66.2)\n",
      "Requirement already satisfied: typing-extensions<5,>=4.7 in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.10.0->langchain_openai) (4.11.0)\n",
      "Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain) (0.6.0)\n",
      "Requirement already satisfied: pydantic-core==2.18.1 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain) (2.18.1)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (3.3.2)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (3.7)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (2.0.7)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (2024.2.2)\n",
      "Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from SQLAlchemy<3,>=1.4->langchain) (3.0.3)\n",
      "Requirement already satisfied: regex>=2022.1.18 in /usr/local/lib/python3.10/dist-packages (from tiktoken<1,>=0.5.2->langchain_openai) (2023.12.25)\n",
      "Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai<2.0.0,>=1.10.0->langchain_openai) (1.2.1)\n",
      "Collecting httpcore==1.* (from httpx<1,>=0.23.0->openai<2.0.0,>=1.10.0->langchain_openai)\n",
      "  Downloading httpcore-1.0.5-py3-none-any.whl (77 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m77.9/77.9 kB\u001b[0m \u001b[31m1.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting h11<0.15,>=0.13 (from httpcore==1.*->httpx<1,>=0.23.0->openai<2.0.0,>=1.10.0->langchain_openai)\n",
      "  Downloading h11-0.14.0-py3-none-any.whl (58 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m58.3/58.3 kB\u001b[0m \u001b[31m2.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting mypy-extensions>=0.3.0 (from typing-inspect<1,>=0.4.0->dataclasses-json<0.7,>=0.5.7->langchain)\n",
      "  Downloading mypy_extensions-1.0.0-py3-none-any.whl (4.7 kB)\n",
      "Installing collected packages: packaging, orjson, mypy-extensions, jsonpointer, h11, typing-inspect, tiktoken, marshmallow, jsonpatch, httpcore, langsmith, httpx, dataclasses-json, openai, langchain-core, langchain-text-splitters, langchain_openai, langchain-community, langchain\n",
      "  Attempting uninstall: packaging\n",
      "    Found existing installation: packaging 24.0\n",
      "    Uninstalling packaging-24.0:\n",
      "      Successfully uninstalled packaging-24.0\n",
      "Successfully installed dataclasses-json-0.6.4 h11-0.14.0 httpcore-1.0.5 httpx-0.27.0 jsonpatch-1.33 jsonpointer-2.4 langchain-0.1.16 langchain-community-0.0.34 langchain-core-0.1.46 langchain-text-splitters-0.0.1 langchain_openai-0.1.4 langsmith-0.1.51 marshmallow-3.21.1 mypy-extensions-1.0.0 openai-1.23.6 orjson-3.10.1 packaging-23.2 tiktoken-0.6.0 typing-inspect-0.9.0\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "\n",
    "try:\n",
    "    from google.colab import drive, userdata\n",
    "    COLAB = True\n",
    "    print(\"Note: using Google CoLab\")\n",
    "except:\n",
    "    print(\"Note: not using Google CoLab\")\n",
    "    COLAB = False\n",
    "\n",
    "# OpenAI Secrets\n",
    "if COLAB:\n",
    "    os.environ[\"OPENAI_API_KEY\"] = userdata.get('OPENAI_API_KEY')\n",
    "\n",
    "# Install needed libraries in CoLab\n",
    "if COLAB:\n",
    "    !pip install langchain langchain_openai"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "pC9A-LaYhsta"
   },
   "source": [
    "# 2.4: Tracking Prompts in Software Development\n",
    "\n",
    "Prompting will undoubtedly become an essential part of modern software engineering. Programmers will likely construct individual prompts to generate methods. In this part, we will see how to write a prompt that consistently produces a non-trivial image cropping function. We will also store the prompt with the function so we do not lose the prompt in the future. Additionally, we will use automated unit tests to ensure that the generated method initially does what we expect, and continues to do so in the future, even if the technique is regenerated.\n",
    "\n",
    "## Conversational Code Generation\n",
    "\n",
    "We will continue to use the conversational code generation function provided in Module 2.2.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "id": "TMF-rtxgRAea"
   },
   "outputs": [],
   "source": [
    "from langchain.chains import ConversationChain\n",
    "from langchain.memory import ConversationBufferWindowMemory\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_core.prompts.chat import PromptTemplate\n",
    "from IPython.display import display_markdown\n",
    "\n",
    "MODEL = 'gpt-4o-mini'\n",
    "TEMPLATE = \"\"\"The following is a friendly conversation between a human and an\n",
    "AI to generate Python code. If you have notes about the code, place them before\n",
    "the code. Any nots about execution should follow the code. If you do mix any\n",
    "notes with the code, make them comments. Add proper comments to the code.\n",
    "Sort imports and follow PEP-8 formatting.\n",
    "\n",
    "Current conversation:\n",
    "{history}\n",
    "Human: {input}\n",
    "Code Assistant:\"\"\"\n",
    "PROMPT_TEMPLATE = PromptTemplate(input_variables=[\"history\", \"input\"], template=TEMPLATE)\n",
    "\n",
    "def start_conversation():\n",
    "    # Initialize the OpenAI LLM with your API key\n",
    "    llm = ChatOpenAI(\n",
    "        model=MODEL,\n",
    "        temperature=0.0,\n",
    "        n=1\n",
    "    )\n",
    "\n",
    "    # Initialize memory and conversation\n",
    "    memory = ConversationBufferWindowMemory()\n",
    "    conversation = ConversationChain(\n",
    "        prompt=PROMPT_TEMPLATE,\n",
    "        llm=llm,\n",
    "        memory=memory,\n",
    "        verbose=False\n",
    "    )\n",
    "\n",
    "    return conversation\n",
    "\n",
    "def generate_code(conversation, prompt):\n",
    "    print(\"Model response:\")\n",
    "    output = conversation.invoke(prompt)\n",
    "    display_markdown(output['response'], raw=True)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ze2LsFCScSQW"
   },
   "source": [
    "## Prompts for Consistent Code Generation\n",
    "\n",
    "The code below shows the prompt I created to generate the clipping function. It is essential to specify very clearly what you want from the LLM. In this case, I made sure to specify:\n",
    "\n",
    "* The name of the function\n",
    "* The name of all arguments to that function\n",
    "* What to return\n",
    "* The exact algorithm, including how to handle negative values\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 799
    },
    "id": "6bkupDjIbni5",
    "outputId": "ecdeeb80-5e60-420d-9985-4304e6eae56a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model response:\n"
     ]
    },
    {
     "data": {
      "text/markdown": [
       "```python\n",
       "import numpy as np\n",
       "\n",
       "def safe_clip(cv2_image, x, y, width, height, background):\n",
       "    \"\"\"\n",
       "    Clips a region from an OpenCV image, adjusting for boundaries and filling missing areas.\n",
       "\n",
       "    Parameters:\n",
       "        cv2_image (numpy.ndarray): The source image from which to clip the region.\n",
       "        x (int): The x-coordinate of the top-left corner of the clipping region.\n",
       "        y (int): The y-coordinate of the top-left corner of the clipping region.\n",
       "        width (int): The width of the clipping region.\n",
       "        height (int): The height of the clipping region.\n",
       "        background (tuple): A tuple representing the BGR color (e.g., (255, 255, 255) for white) used to fill missing areas.\n",
       "\n",
       "    Returns:\n",
       "        tuple: A tuple containing:\n",
       "            - new_image (numpy.ndarray): The new image with the clipped region and background.\n",
       "            - x_offset (int): The x-coordinate offset of the new image relative to the original image.\n",
       "            - y_offset (int): The y-coordinate offset of the new image relative to the original image.\n",
       "\n",
       "    Example:\n",
       "        >>> img = np.zeros((100, 100, 3), dtype=np.uint8)\n",
       "        >>> clipped_img, x_off, y_off = safe_clip(img, -10, -10, 120, 120, (255, 0, 0))\n",
       "        >>> print(clipped_img.shape, x_off, y_off)\n",
       "        ((120, 120, 3), 10, 10)\n",
       "    \"\"\"\n",
       "    # Calculate effective x, y, width, and height considering image boundaries\n",
       "    x_eff = max(x, 0)\n",
       "    y_eff = max(y, 0)\n",
       "    width_eff = min(width, cv2_image.shape[1] - x_eff)\n",
       "    height_eff = min(height, cv2_image.shape[0] - y_eff)\n",
       "\n",
       "    # Create a new image filled with the background color\n",
       "    new_image = np.full((height, width, 3), background, dtype=cv2_image.dtype)\n",
       "\n",
       "    # Calculate offsets if the requested region is out of the original image bounds\n",
       "    x_offset = -min(x, 0)\n",
       "    y_offset = -min(y, 0)\n",
       "\n",
       "    # Place the clipped part of the original image into the new image\n",
       "    new_image[y_offset:y_offset + height_eff, x_offset:x_offset + width_eff] = \\\n",
       "        cv2_image[y_eff:y_eff + height_eff, x_eff:x_eff + width_eff]\n",
       "\n",
       "    return new_image, x_offset, y_offset\n",
       "```"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "conversation = start_conversation()\n",
    "generate_code(conversation,\"\"\"\n",
    "Write a Python function named safe_clip using the PEP-8 style guide. The function\n",
    "should accept five parameters: cv2_image, x, y, width, height, and background.\n",
    "The imports should be sorted alphabetically.\n",
    "\n",
    "The purpose of this function is to clip a region from an OpenCV image while a\n",
    "djusting for boundaries and filling any areas missing from the original image\n",
    "boundaries with a specified color. Ensure that the code includes comments and a\n",
    "docstring explaining the functionality in detail.\n",
    "\n",
    "The function should:\n",
    "\n",
    "Calculate the dimensions of the clipping region and adjust them if they extend\n",
    "beyond the image boundaries. Negative x and y indicate that the source image\n",
    "must be expanded to accomodate the these coordinate that were outside the image.\n",
    "Create a new image of the specified size, filled with the background color.\n",
    "Place the clipped region into the newly created image at the appropriate location.\n",
    "Return a tuple containing the new image, along with x and y offsets indicating\n",
    "how much the origin of the clipped image has shifted relative to the original image.\n",
    "The return type should be specified in the function's docstring, which should\n",
    "also detail the types and purposes of each parameter. Additionally, mention that\n",
    "the function will adjust coordinates and size to fit within the image dimensions and\n",
    "fill missing areas with the specified background color.\n",
    "\n",
    "Include a detailed example in the docstring illustrating how the function is\n",
    "called and what it returns.\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "OCmjBxIdcesJ"
   },
   "source": [
    "## Trying Out the Generated Code\n",
    "\n",
    "The code below shows the prompt I created to generate the clipping function. It is essential to specify very clearly what you want from the LLM. In this case, I made sure to specify:\n",
    "\n",
    "* The name of the function\n",
    "* The name of all arguments to that function\n",
    "* What to return\n",
    "* The exact algorithm, including how to handle negative values\n",
    "\n",
    "The following code shows my function; I also embed the prompt inside the source as a comment. This way, I can track changes to both the function and prompt as I check both in a source code repository like GitHub."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "id": "q42Lx21EcjTf"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "## safe_clip was generated by the following prompt:\n",
    "# Write a Python function named safe_clip using the PEP-8 style guide. The function\n",
    "# should accept five parameters: cv2_image, x, y, width, height, and background.\n",
    "# The imports should be sorted alphabetically.\n",
    "#\n",
    "# The purpose of this function is to clip a region from an OpenCV image while a\n",
    "# djusting for boundaries and filling any areas missing from the original image\n",
    "# boundaries with a specified color. Ensure that the code includes comments and a\n",
    "# docstring explaining the functionality in detail.\n",
    "#\n",
    "# The function should:\n",
    "#\n",
    "# Calculate the dimensions of the clipping region and adjust them if they extend\n",
    "# beyond the image boundaries. Negative x and y indicate that the source image\n",
    "# must be expanded to accomodate the these coordinate that were outside the image.\n",
    "# Create a new image of the specified size, filled with the background color.\n",
    "# Place the clipped region into the newly created image at the appropriate location.\n",
    "# Return a tuple containing the new image, along with x and y offsets indicating\n",
    "# how much the origin of the clipped image has shifted relative to the original image.\n",
    "# The return type should be specified in the function's docstring, which should\n",
    "# also detail the types and purposes of each parameter. Additionally, mention that\n",
    "# the function will adjust coordinates and size to fit within the image dimensions and\n",
    "# fill missing areas with the specified background color.\n",
    "#\n",
    "# Include a detailed example in the docstring illustrating how the function is\n",
    "# called and what it returns.\n",
    "\n",
    "def safe_clip(cv2_image, x, y, width, height, background):\n",
    "    \"\"\"\n",
    "    Clips a region from an OpenCV image, adjusting for boundaries and filling missing areas.\n",
    "\n",
    "    Parameters:\n",
    "        cv2_image (numpy.ndarray): The source image from which to clip the region.\n",
    "        x (int): The x-coordinate of the top-left corner of the clipping region.\n",
    "        y (int): The y-coordinate of the top-left corner of the clipping region.\n",
    "        width (int): The width of the clipping region.\n",
    "        height (int): The height of the clipping region.\n",
    "        background (tuple): A tuple representing the BGR color (e.g., (255, 255, 255) for white) used to fill missing areas.\n",
    "\n",
    "    Returns:\n",
    "        tuple: A tuple containing:\n",
    "            - new_image (numpy.ndarray): The new image with the clipped region and background.\n",
    "            - x_offset (int): The x-coordinate offset of the new image relative to the original image.\n",
    "            - y_offset (int): The y-coordinate offset of the new image relative to the original image.\n",
    "\n",
    "    Example:\n",
    "        >>> img = np.zeros((100, 100, 3), dtype=np.uint8)\n",
    "        >>> clipped_img, x_off, y_off = safe_clip(img, -10, -10, 120, 120, (255, 0, 0))\n",
    "        >>> print(clipped_img.shape, x_off, y_off)\n",
    "        ((120, 120, 3), 10, 10)\n",
    "    \"\"\"\n",
    "    # Calculate effective x, y, width, and height considering image boundaries\n",
    "    x_eff = max(x, 0)\n",
    "    y_eff = max(y, 0)\n",
    "    width_eff = min(width, cv2_image.shape[1] - x_eff)\n",
    "    height_eff = min(height, cv2_image.shape[0] - y_eff)\n",
    "\n",
    "    # Create a new image filled with the background color\n",
    "    new_image = np.full((height, width, 3), background, dtype=cv2_image.dtype)\n",
    "\n",
    "    # Calculate offsets if the requested region is out of the original image bounds\n",
    "    x_offset = -min(x, 0)\n",
    "    y_offset = -min(y, 0)\n",
    "\n",
    "    # Place the clipped part of the original image into the new image\n",
    "    new_image[y_offset:y_offset + height_eff, x_offset:x_offset + width_eff] = \\\n",
    "        cv2_image[y_eff:y_eff + height_eff, x_eff:x_eff + width_eff]\n",
    "\n",
    "    return new_image, x_offset, y_offset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "0U3wWY3jv1it"
   },
   "source": [
    "Now, I will create an image of a grayscale and show how the function performs on three different types of crops. One of the critical features of this cropping function is that it might expand the original image if the upper-left corner or lower-right corner is off of the original image."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 328
    },
    "id": "DcmhBIZQclni",
    "outputId": "78fd1db1-c906-4780-8255-7071709a818c"
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAABOwAAAE3CAYAAAAZhN7OAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAyYklEQVR4nO3de7xVc/4/8Pc+pU5OdSqihG7KtTEmE93LZXIJ+UkKqdwNGaMMw5DbCDPN5IFB4zKGXENMbsPIpdzvZFBGjS9DKcklUufz+6POdvY5pzqltMw8n4/HfpyzP+uz1vqstc/+nM9+7XXJpZRSAAAAAACZULSuGwAAAAAAfEtgBwAAAAAZIrADAAAAgAwR2AEAAABAhgjsAAAAACBDBHYAAAAAkCECOwAAAADIEIEdAAAAAGSIwA4AAAAAMkRg9wNw9tlnRy6XW615//KXv0Qul4uZM2eu2UZVMHPmzMjlcvGXv/xlra0DWLtatWoVQ4cOzT9/9NFHI5fLxaOPPrrO2lQT36V/XFvrKa/78ccfr7Ru5f3+v87/E/4X6X/Xnf+GbYA1QT+07tR0LPh9fK7/oflfGEcL7NaiadOmxaGHHhotWrSIunXrxiabbBKHHHJITJs2bV03bZ0o7/gnTJiwrpsC/zPeeeedOOaYY6JNmzZRXFwcDRs2jK5du8Yll1wSCxcuXNfN+178/Oc/j6Kiopg3b15B+bx586KoqCjq1q0bX331VcG0f/3rX5HL5eL0009f7nIvuOCCmDhx4tpo8morH7iWP4qKiqJ58+bRt2/fePrpp9d18+B/iv63qgEDBkQul4tTTz212un33XdfnH322TVeXq9evQr6vIqPrbbaag21Gn649EOFJk2aFHvssUdssMEGUVxcHO3bt4+RI0fG3LlzV3uZH3zwQZx99tnx8ssvr7mGfg/KP5tXfDRp0iR23nnnGD9+/LpuHsvUXtcN+G915513xqBBg6JJkyZxxBFHROvWrWPmzJlxzTXXxIQJE+KWW26J/fffv0bL+s1vfhOnnXbaarVj8ODBMXDgwKhbt+5qzQ/8cN17771x4IEHRt26deOwww6L7bbbLhYtWhRTpkyJU045JaZNmxbjxo2rdt4ePXrEwoULo06dOt9zq9e8bt26xRVXXBFTp06NffbZJ1/+5JNPRlFRUXzzzTfx/PPPR7du3fLTpk6dmp83ovp++IILLoj+/ftHv379Vrttb731VhQVrfnvzq644oqoX79+lJWVxXvvvRd//vOfo0ePHvHss8/Gj3/84zW+PqCQ/reqBQsWxN/+9rdo1apV3HzzzXHhhRdWOTLmvvvui8svv3yVQrtNN900Ro8eXaW8tLT0uzYZftD0Q4VGjhwZY8aMie233z5OPfXUaNKkSbz44otx2WWXxS233BL/+Mc/Ysstt1zl5X7wwQdxzjnnRKtWrVZrjLW2xoI1deKJJ8ZPf/rTiIiYO3du3HrrrXHooYfG/Pnz4/jjj19n7WIpgd1a8M4778TgwYOjTZs28fjjj0fTpk3z037xi19E9+7dY/DgwfHqq69GmzZtlrucL774IkpKSqJ27dpRu/bqvVS1atWKWrVqrda8wA/Xu+++GwMHDoyWLVvGI488Es2bN89PO/7442PGjBlx7733Lnf+oqKiKC4u/j6autaVh25TpkwpCOymTp0aP/rRj2LhwoUxZcqUgsBuypQpUVRUFF26dImI+E798IqsrS9T+vfvHxtuuGH+eb9+/WK77baL22+/XWAHa5n+t3p33HFHLFmyJK699trYZZdd4vHHH4+ePXt+5+WWlpbGoYceugZaCP899EOFbr755hgzZkwcdNBBMX78+ILPx0OHDo3evXvHgQceGC+++OJaGe+tyLo+sKZ79+7Rv3///PPjjjsu2rRpEzfddJPALgOcErsW/O53v4svv/wyxo0bVxDWRURsuOGGcdVVV8UXX3wRF198cb68/DSmN954Iw4++OBo3Lhx/sNjdefmL1y4ME488cTYcMMNo0GDBrHvvvvG+++/H7lcruBbyerOdW/VqlX07ds3pkyZEp06dYri4uJo06ZN/PWvfy1Yx7x582LkyJHRoUOHqF+/fjRs2DD23HPPeOWVV9bQnvp2295+++049NBDo7S0NJo2bRpnnnlmpJTivffei/322y8aNmwYzZo1izFjxhTMv2jRojjrrLOiY8eOUVpaGiUlJdG9e/eYPHlylXXNnTs3Bg8eHA0bNoxGjRrFkCFD4pVXXqn2eklvvvlm9O/fP5o0aRLFxcWx4447xj333LPGthvWtosvvjg+//zzuOaaawoGaeW22GKL+MUvfrHc+au7dkmvXr1iu+22ixdeeCG6dOkS9erVi9atW8eVV15Z7by33nprnH766dGsWbMoKSmJfffdN957770q63rmmWdijz32iNLS0lh//fWjZ8+e+SPcKpoyZUr89Kc/jeLi4mjbtm1cddVVNdoXm2++eWy22WZVljl16tTo2rVrdOnSpdpp2267bTRq1CgiqvbDuVwuvvjii7j++uvzpxFUvobG/PnzY+jQodGoUaMoLS2NYcOGxZdffllQp/K1N8r77KlTp8bJJ58cTZs2jZKSkth///1jzpw5Ndre6jRr1iwiosogdPbs2XHEEUfExhtvHMXFxbH99tvH9ddfX1Bnedexqe56c0OHDo369evH+++/H/369Yv69etH06ZNY+TIkbFkyZKC+cv3T2lpab5Pnj9/fpW2f/jhhzFs2LDYdNNNo27dutG8efPYb7/9XMOFzNL/Vm/8+PGx++67R+/evWPrrbeucsrV0KFD4/LLL4+IKDhFa02p6TbUdIwdEfH+++/H4YcfHhtvvHHUrVs3tt1227j22mvXWJthdemHCp1zzjnRuHHjGDduXJWDWTp16hSnnnpqvPbaawWXblre9dF69eoVvXr1ym9r+dFpw4YNy/db5WOj6dOnxwEHHBDNmjWL4uLi2HTTTWPgwIHx6aefrnA906ZNi1122SXq1asXm266aZx//vlRVlZW7bbdf//90b179ygpKYkGDRrE3nvv/Z0uwVWnTp1o3LhxlTHj4sWL47zzzou2bdtG3bp1o1WrVnH66afH119/XVCvur6yuu1clTFvSinOP//82HTTTWP99deP3r17V7uN33zzTZxzzjnRrl27KC4ujg022CC6desWDz300Grvj3XNEXZrQfnh/t27d692eo8ePaJVq1bVfqtx4IEHRrt27eKCCy6IlNJy1zF06NC47bbbYvDgwbHzzjvHY489FnvvvXeN2zhjxozo379/HHHEETFkyJC49tprY+jQodGxY8fYdtttI2LpNZwmTpwYBx54YLRu3To++uijuOqqq6Jnz57xxhtvxCabbFLj9a3MQQcdFFtvvXVceOGFce+998b5558fTZo0iauuuip22WWXuOiii2L8+PExcuTI+OlPfxo9evSIiKWnV1x99dUxaNCgOOqoo+Kzzz6La665Jvr06VNw6ldZWVnss88+8eyzz8Zxxx0XW221Vdx9990xZMiQKm2ZNm1adO3aNVq0aBGnnXZalJSUxG233Rb9+vWLO+64o8anMsO69Le//S3atGmTP0JsTfnkk09ir732igEDBsSgQYPitttui+OOOy7q1KkThx9+eEHd3/72t/lrFc2ePTvGjh0bu+22W7z88stRr169iIh45JFHYs8994yOHTvGqFGjoqioKK677rrYZZdd4oknnohOnTpFRMRrr70WP/vZz6Jp06Zx9tlnx+LFi2PUqFGx8cYb16jd3bp1izvvvDO+/vrrqFu3bixatCiee+65OO644+LLL7+MX/3qV5FSilwuF5988km88cYbceyxxy53eTfccEMceeSR0alTpzj66KMjIqJt27YFdQYMGBCtW7eO0aNHx4svvhhXX311bLTRRnHRRRettL3Dhw+Pxo0bx6hRo2LmzJkxduzYOOGEE+LWW2+t0faWX6+vrKws3n///TjvvPOiuLg4BgwYkK+zcOHC6NWrV8yYMSNOOOGEaN26ddx+++0xdOjQmD9//goH8iuyZMmS6NOnT+y0007x+9//Ph5++OEYM2ZMtG3bNo477riIWDrw2m+//WLKlClx7LHHxtZbbx133XVXtX3yAQccENOmTYvhw4dHq1atYvbs2fHQQw/Fv//972jVqtVqtRHWJv1vVR988EFMnjw5/4XAoEGD4o9//GNcdtll+VPujjnmmPjggw/ioYceihtuuKHGy16yZEm1N/mpV69elJSUrPI21HSM/dFHH8XOO+8cuVwuTjjhhGjatGncf//9ccQRR8SCBQvipJNOqvE2wJqmH/rW9OnT46233oqhQ4dGw4YNq61z2GGHxahRo2LSpEkxcODAGu+PrbfeOs4999w466yz4uijj85//u/SpUssWrQo+vTpE19//XUMHz48mjVrFu+//35MmjQp5s+fv9zT9j/88MPo3bt3LF68OP9ZdNy4cfl9VtENN9wQQ4YMiT59+sRFF10UX375ZVxxxRXRrVu3eOmll2o0Tvrss8/yfei8efPipptuitdffz2uueaagnpHHnlkXH/99dG/f/8YMWJEPPPMMzF69Oj45z//GXfddVeN91llNRnznnXWWXH++efHXnvtFXvttVe8+OKL8bOf/SwWLVpUsKyzzz47Ro8enR+jL1iwIJ5//vl48cUXY/fdd1/tNq5TiTVq/vz5KSLSfvvtt8J6++67b4qItGDBgpRSSqNGjUoRkQYNGlSlbvm0ci+88EKKiHTSSScV1Bs6dGiKiDRq1Kh82XXXXZciIr377rv5spYtW6aISI8//ni+bPbs2alu3bppxIgR+bKvvvoqLVmypGAd7777bqpbt24699xzC8oiIl133XUr3ObJkyeniEi33357lW07+uij82WLFy9Om266acrlcunCCy/Ml3/yySepXr16aciQIQV1v/7664L1fPLJJ2njjTdOhx9+eL7sjjvuSBGRxo4dmy9bsmRJ2mWXXaq0fdddd00dOnRIX331Vb6srKwsdenSJbVr126F2whZ8Omnn9aoH6qoZcuWBe+t8vfr5MmT82U9e/ZMEZHGjBmTL/v666/Tj3/847TRRhulRYsWFczbokWLfB+XUkq33XZbioh0ySWXpJSWvq/atWuX+vTpk8rKyvL1vvzyy9S6deu0++6758v69euXiouL06xZs/Jlb7zxRqpVq1aqyb+yyy+/PEVEeuKJJ1JKKT311FMpItKsWbPSG2+8kSIiTZs2LaWU0qRJk1JEpPHjx+fnr9wPp5RSSUlJwT6rXLdiH5RSSvvvv3/aYIMNCsoq7/fyPnu33XYr2Ce//OUvU61atdL8+fNXuJ3l6678aNSoUXrggQcK6o4dOzZFRLrxxhvzZYsWLUqdO3dO9evXz7921f0tpFR93z9kyJAUEQX/I1JKaYcddkgdO3bMP584cWKKiHTxxRfnyxYvXpy6d+9esMxPPvkkRUT63e9+t8LthqzQ/1bv97//fapXr16+TW+//XaKiHTXXXcV1Dv++ONrvMyUvt0v1T2OOeaYVd6GVRljH3HEEal58+bp448/Lqg7cODAVFpamr788ssabwesSfqhQuVjjj/+8Y8rrNewYcP0k5/8ZLn7pOJ+6NmzZ/75c889V+1n4ZdeeqnKZ9/qVF7PSSedlCIiPfPMM/my2bNnp9LS0oLP9Z999llq1KhROuqoowqW9+GHH6bS0tIq5ZWVv06VH0VFRem3v/1tQd2XX345RUQ68sgjC8pHjhyZIiI98sgj+bLKfeXytrOmY97Zs2enOnXqpL333rug3umnn54iomCZ22+/fdp7771XuN0/NE6JXcM+++yziIho0KDBCuuVT1+wYEFB+YqO6Cj3wAMPRMTSOx9WNHz48Bq3c5tttik4ArBp06ax5ZZbxr/+9a98Wd26dfMXwFyyZEnMnTs36tevH1tuuWW8+OKLNV5XTRx55JH532vVqhU77rhjpJTiiCOOyJc3atSoShtr1aqV/2a2rKws5s2bF4sXL44dd9yxoI0PPPBArLfeenHUUUfly4qKiqqclz9v3rx45JFHYsCAAflvGz7++OOYO3du9OnTJ6ZPnx7vv//+Gt12WNPK+5WV9UOro3bt2nHMMcfkn9epUyeOOeaYmD17drzwwgsFdQ877LCCNvTv3z+aN28e9913X0REvPzyyzF9+vQ4+OCDY+7cufn32xdffBG77rprPP7441FWVhZLliyJBx98MPr16xebb755fnlbb7119OnTp0btrngdu4ilp7y2aNEiNt9889hqq62iSZMm+dMvKt9wYnVV7s+7d+8ec+fOrdLvV+foo48uOBWse/fusWTJkpg1a1aN1n3HHXfEQw89FH//+9/juuuui/bt28cBBxwQTz75ZL7OfffdF82aNYtBgwbly9Zbb7048cQT4/PPP4/HHnusRuuqTnXbXrHvvu+++6J27dr5I+4ilvbnlf+P1atXL+rUqROPPvpofPLJJ6vdHvi+6H+rN378+Nh7773zbWrXrl107NhxjdyJsFWrVvHQQw9VeZQf4bYq21DTMXZKKe64447YZ599IqWU338ff/xx9OnTJz799NM1PlaGmtIPFVqVz+c1GaPVVPkRdA8++GCVS6KsyH333Rc777xz/ujCiKWf1Q855JCCeg899FDMnz8/Bg0aVNAH1apVK3baaadqLxFVnbPOOivfb956660xaNCgOOOMM+KSSy4paFNExMknn1ww74gRIyIiVng9xJVZ2Zj34YcfjkWLFsXw4cML6lV3FHOjRo1i2rRpMX369NVuT9Y4JXYNK+8IyjuG5Vlex9G6deuVrmPWrFlRVFRUpe4WW2xR43ZW7OzKNW7cuOADUVlZWVxyySXxpz/9Kd59992C6w9tsMEGNV7X6rSntLQ0iouLCy6aXl5e+bbb119/fYwZMybefPPN+Oabb/LlFffPrFmzonnz5rH++usXzFt5n82YMSNSSnHmmWfGmWeeWW1bZ8+eHS1atKj5xsH3rPxw/5X1Q6tjk002yZ9iVK59+/YRsfSaZjvvvHO+vF27dgX1crlcbLHFFvlrj5X/M63uNMhyn376aXz99dexcOHCKsuLiNhyyy3zg4gV2W677aJRo0YFoVzXrl3z7ercuXNMnTo1jjrqqJg6dWpsttlm1faTq6Ly/I0bN46IpaeTLO+UjJrMWxM9evQo6D/79+8f7dq1i+HDh+cH1LNmzYp27dpVuTPZ1ltvnZ++OoqLi6tcv7Xy/5fyPrl+/foF9Srfna1u3bpx0UUXxYgRI2LjjTeOnXfeOfr27RuHHXZY/rp8kCX636r++c9/xksvvRSHHXZYzJgxI1/eq1evuPzyy2PBggUr7RNXpKSkJHbbbbflTp8zZ06Nt6GmY+w5c+bE/PnzY9y4ccu9y+bs2bNXZTNgjdEPFVqVz+cbbbTRCuusitatW8fJJ58cf/jDH2L8+PHRvXv32HffffPXbV+eWbNmxU477VSlvPIYqXz/7bLLLtUup6b9aocOHQr60AEDBsSnn34ap512Whx88MHRtGnTfN9YuS9s1qxZNGrUaLXHjBErH/OWL7vy69+0adN83XLnnntu7LffftG+ffvYbrvtYo899ojBgwfHj370o9Vu37omsFvDSktLo3nz5vHqq6+usN6rr74aLVq0qPJGqu7c9LVheXeOTRWum3fBBRfEmWeeGYcffnicd9550aRJkygqKoqTTjppuRe9XJPtqUkbb7zxxhg6dGj069cvTjnllNhoo42iVq1aMXr06HjnnXdWuR3l2zVy5MjlfmOzKsEorAsNGzaMTTbZJF5//fV13ZQVKn+//e53v1vunUvr169f5WK2q6OoqCg6d+4cTz75ZKSUYurUqXH66afnp3fp0iWuvfba/LXt+vXr953XWZM+bG3MW5369evHTjvtFHfffXf+DuQ1tbyLvle+iUS5NX1n8pNOOin22WefmDhxYjz44INx5plnxujRo+ORRx6JHXbYYY2uC74r/W9VN954Y0RE/PKXv4xf/vKXVabfcccdMWzYsO+8nu9T+f479NBDlxs2/JA/IPLDph8qVP5F5Io+n8+aNSsWLFgQ22yzTb5sReOfmo51xowZE0OHDo277747/v73v8eJJ54Yo0ePjqeffjo23XTTVdiKqsr33w033FDtl5jf5W63u+66a0yaNCmeffbZgmt4fpcbAa3quHF1xrw9evSId955J7+/r7766vjjH/8YV155ZcEZfT8kAru1oG/fvvHnP/85pkyZUu0pVU888UTMnDmz4HDiVdGyZcsoKyuLd999tyBprvit5ZowYcKE6N27d5ULTs6fP7/KkW/ryoQJE6JNmzZx5513FnQgo0aNKqjXsmXLmDx5cnz55ZcFR9lV3mdt2rSJiKWnha3o21rIur59+8a4cePiqaeeis6dO6+x5X7wwQdVAp+33347IqLKhW0rH46eUooZM2bkP8SU36ShYcOGK3y/NW3aNOrVq1ft4e1vvfVWjdverVu3uP/+++Oee+6J2bNn54+wi1ga2J1xxhlx3333xcKFC2t0OuyavHvh92Hx4sUREfH5559HSUlJtGzZMl599dUoKysrOMruzTffjIil/WbEt990Vr6D63f5NrVly5bxj3/8Iz7//POCo+yW93q2bds2RowYESNGjIjp06fHj3/84xgzZkw+CIAs0f8Wrvemm26K3r17VznNNCLivPPOi/Hjx+cDu7XRr67KNtR0jN20adNo0KBBLFmyxHiRTNIPfat9+/bRvn37mDhxYlxyySXVnhr717/+NSKW7rdyjRs3rvbu9bNmzcp/ZoxYeb/VoUOH6NChQ/zmN7+JJ598Mrp27RpXXnllnH/++dXWb9myZY22tXz/bbTRRmu8H6o4ZixvU1lZWUyfPj0fgEYsvfnO/Pnz82PGiOr326JFi+I///nParWlfNnTp08v2O9z5syp9syTJk2axLBhw2LYsGHx+eefR48ePeLss8/+wQZ2rmG3FpxyyilRr169OOaYY6qcvjlv3rw49thjY/31149TTjlltZZffuTXn/70p4LySy+9dPUavBy1atWqkmzffvvtmbqGW3kiX7GdzzzzTDz11FMF9fr06RPffPNN/PnPf86XlZWVxeWXX15Qb6ONNopevXrFVVddVW2nUvkW05BVv/rVr6KkpCSOPPLI+Oijj6pMf+eddwquTVFTixcvjquuuir/fNGiRXHVVVdF06ZNo2PHjgV1//rXvxacfjBhwoT4z3/+E3vuuWdERHTs2DHatm0bv//97/MDgorK32+1atWKPn36xMSJE+Pf//53fvo///nPePDBB2vc9vIQ7qKLLor111+/4NvcTp06Re3atePiiy8uqLsiJSUl1Q7ksmjevHnx5JNPRrNmzfKne+y1117x4YcfFtyFa/HixXHppZdG/fr1o2fPnhGxdKBUq1atePzxxwuWWfl/0KrYa6+9YvHixXHFFVfky5YsWVLl/9iXX34ZX331VUFZ27Zto0GDBmvkyB9YG/S/35o6dWrMnDkzhg0bFv3796/yOOigg2Ly5MnxwQcfRETkQ4A12beuyjbUdIxdq1atOOCAA+KOO+6o9igm40XWNf1QobPOOis++eSTOPbYY6sc6fXCCy/ERRddFNttt10ccMAB+fK2bdvG008/XXAn0kmTJsV7771XMP/y+q0FCxbkg69yHTp0iKKiohWOYfbaa694+umn49lnn82XzZkzp8o1P/v06RMNGzaMCy64oOCyUBXnWV2TJk2KiIjtt98+36aIiLFjxxbU+8Mf/hARUXAUXtu2bauMGceNG7fcI+xWZrfddov11lsvLr300oLP/JXbEhFVspf69evHFlts8YMeMzrCbi1o165dXH/99XHIIYdEhw4d4ogjjojWrVvHzJkz45prromPP/44br755nwqvqo6duwYBxxwQIwdOzbmzp2bv+V8+bcba+rbyb59+8a5554bw4YNiy5dusRrr70W48ePL0i217W+ffvGnXfeGfvvv3/svffe8e6778aVV14Z22yzTUHH369fv+jUqVOMGDEiZsyYEVtttVXcc889MW/evIgo3GeXX355dOvWLTp06BBHHXVUtGnTJj766KN46qmn4v/+7//ilVde+d63E1ZV27Zt46abboqDDjoott566zjssMNiu+22i0WLFsWTTz4Zt99+ewwdOnSVl7vJJpvERRddFDNnzoz27dvHrbfeGi+//HKMGzcu1ltvvYK6TZo0iW7dusWwYcPio48+irFjx8YWW2yRv/lLUVFRXH311bHnnnvGtttuG8OGDYsWLVrE+++/H5MnT46GDRvG3/72t4iIOOecc+KBBx6I7t27x89//vN8sLTtttuu9BIE5Tp16hR16tSJp556Knr16lVwqsD6668f22+/fTz11FPRqFGj2G677Va6vI4dO8bDDz8cf/jDH2KTTTaJ1q1bV3vNkXVhwoQJUb9+/UgpxQcffBDXXHNNfPLJJ3HllVfm+7ujjz46rrrqqhg6dGi88MIL0apVq5gwYUJMnTo1xo4dm/8GurS0NA488MC49NJLI5fLRdu2bWPSpEnf6fpM++yzT3Tt2jVOO+20mDlzZmyzzTZx5513xqefflpQ7+23345dd901BgwYENtss03Url077rrrrvjoo49i4MCBq7+DYC3S/35r/PjxUatWrYIPcxXtu+++ccYZZ8Qtt9wSJ598cv4D/4knnhh9+vSJWrVqrfS9/umnny73aNtDDz10lbZhVcbYF154YUyePDl22mmnOOqoo2KbbbaJefPmxYsvvhgPP/xwfowJ64J+qNAhhxwSzz33XFxyySXxxhtvxCGHHBKNGzeOF198Ma699trYYIMNYsKECQXbcOSRR8aECRNijz32iAEDBsQ777wTN954Y5XP8G3bto1GjRrFlVdeGQ0aNIiSkpLYaaed4pVXXokTTjghDjzwwGjfvn0sXrw4brjhhnzgvzy/+tWv4oYbbog99tgjfvGLX0RJSUmMGzcuf2ZEuYYNG8YVV1wRgwcPjp/85CcxcODAaNq0afz73/+Oe++9N7p27RqXXXbZSvfNE088kf9ydN68eXHPPffEY489FgMHDoytttoqIpYGd0OGDIlx48bF/Pnzo2fPnvHss8/G9ddfH/369YvevXsX7Ldjjz02DjjggNh9993jlVdeiQcffHC1z9Br2rRpjBw5MkaPHh19+/aNvfbaK1566aW4//77qyxzm222iV69ekXHjh2jSZMm8fzzz8eECRPihBNOWK11Z8L3fVva/yWvvvpqGjRoUGrevHlab731UrNmzdKgQYPSa6+9VqXuqFGjUkSkOXPmLHdaRV988UU6/vjjU5MmTVL9+vVTv3790ltvvZUiIl144YX5euW3Sy6//XNKS2+pXN3tjivfovqrr75KI0aMSM2bN0/16tVLXbt2TU899VSVeu+++261t7KurPzW0RVvbb287R4yZEgqKSmpto3bbrtt/nlZWVm64IILUsuWLVPdunXTDjvskCZNmpSGDBmSWrZsWTDvnDlz0sEHH5waNGiQSktL09ChQ9PUqVNTRKRbbrmloO4777yTDjvssNSsWbO03nrrpRYtWqS+ffumCRMmrHAbIWvefvvtdNRRR6VWrVqlOnXqpAYNGqSuXbumSy+9NH311Vf5epVvtV7+fp08eXK+rPz99/zzz6fOnTun4uLi1LJly3TZZZcVrLN83ptvvjn9+te/ThtttFGqV69e2nvvvdOsWbOqtPGll15K/+///b+0wQYbpLp166aWLVumAQMGpH/84x8F9R577LHUsWPHVKdOndSmTZt05ZVXVts/rkjnzp1TRKTTTz+9yrQTTzwxRUTac889q0yrbj1vvvlm6tGjR6pXr17BbeWX168trz+u7hb3zz33XMG81b0e1Slfd8VHSUlJ6ty5c7rtttuq1P/oo4/SsGHD0oYbbpjq1KmTOnToUG1fPmfOnHTAAQek9ddfPzVu3Dgdc8wx6fXXX6/S9y+v765u/82dOzcNHjw4NWzYMJWWlqbBgwenl156qWCZH3/8cTr++OPTVlttlUpKSlJpaWnaaaedqt0WyJr/9f530aJFaYMNNkjdu3df4X5q3bp12mGHHVJKKS1evDgNHz48NW3aNOVyuZX27z179qzS51V8rM421HSMndLSPvT4449Pm222WX6sv+uuu6Zx48atsN3wfflf74cqmzhxYtp9991T48aNU926ddMWW2yRRowYUe1n8JRSGjNmTGrRokWqW7du6tq1a3r++eerfBZOKaW77747bbPNNql27dr5ccy//vWvdPjhh6e2bdum4uLi1KRJk9S7d+/08MMPF8xbed+ntDRH6NmzZyouLk4tWrRI5513XrrmmmuqjCNTWrq/+/Tpk0pLS1NxcXFq27ZtGjp0aHr++edXuC/KX6eKjzp16qStttoq/fa3v02LFi0qqP/NN9+kc845J7Vu3Tqtt956abPNNku//vWvC/6OUkppyZIl6dRTT00bbrhhWn/99VOfPn3SjBkzvtOYd8mSJemcc87J5xK9evVKr7/+epVlnn/++alTp06pUaNGqV69esvdlh+SXEqreQVrMufll1+OHXbYIW688cYqt32mehMnToz9998/pkyZUnA9K6CqXr16xccff7zSixg/+uij0bt377j99tujf//+31PrAP576X/XLWNs0A/BuuAadj9QCxcurFI2duzYKCoqih49eqyDFmVf5X1Wfr2khg0bxk9+8pN11CoAALLCGBuArHANux+oiy++OF544YXo3bt31K5dO+6///64//774+ijj47NNttsXTcvk4YPHx4LFy6Mzp07x9dffx133nlnPPnkk3HBBRdEvXr11nXzAABYx4yxAcgKgd0PVJcuXeKhhx6K8847Lz7//PPYfPPN4+yzz44zzjhjXTcts3bZZZcYM2ZMTJo0Kb766qvYYost4tJLL/1hX4QSAIA1xhgbgKxwDTsAAAAAyBDXsAMAAACADBHYAQAAAECG1PgadrlcruD35T0iIoqKigqe53K5KCoqqtG85XVrMn1Fy6tp3Zosy3bZLtu1enVr1aoV/2123HHHgufl+76iytudtTrVvS6V51vXdbK+D+3n7NRZ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\n",
      "text/plain": [
       "<Figure size 1600x400 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Create a demo image (100x100) with a gradient and a solid square in the center\n",
    "image = np.zeros((100, 100, 3), dtype=np.uint8)\n",
    "cv2.rectangle(image, (30, 30), (70, 70), (255, 255, 255), -1)  # White square\n",
    "for i in range(100):\n",
    "    image[:, i] = i * 2.55  # Gradient from black to white\n",
    "\n",
    "# Define background color\n",
    "background_color = (0, 0, 255)  # Red background\n",
    "\n",
    "# Perform clipping operations\n",
    "clipped_image_within, _, _ = safe_clip(image, 20, 20, 60, 60, background_color)\n",
    "clipped_image_edge, _, _ = safe_clip(image, 50, 50, 100, 100, background_color)\n",
    "clipped_image_outside, _, _ = safe_clip(image, -10, -10, 120, 120, background_color)\n",
    "\n",
    "# Setup matplotlib plots\n",
    "fig, axs = plt.subplots(1, 4, figsize=(16, 4))\n",
    "axs[0].imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))\n",
    "axs[0].set_title('Original Image')\n",
    "axs[0].axis('off')\n",
    "\n",
    "axs[1].imshow(cv2.cvtColor(clipped_image_within, cv2.COLOR_BGR2RGB))\n",
    "axs[1].set_title('Clipped Within Bounds')\n",
    "axs[1].axis('off')\n",
    "\n",
    "axs[2].imshow(cv2.cvtColor(clipped_image_edge, cv2.COLOR_BGR2RGB))\n",
    "axs[2].set_title('Clipped At Edge')\n",
    "axs[2].axis('off')\n",
    "\n",
    "axs[3].imshow(cv2.cvtColor(clipped_image_outside, cv2.COLOR_BGR2RGB))\n",
    "axs[3].set_title('Clipped Outside Bounds')\n",
    "axs[3].axis('off')\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "B5g--vkll2de"
   },
   "source": [
    "## Provide Unit Tests\n",
    "\n",
    "Unit testing is an essential facet of software engineering. Unit testing is necessary when a prompt generates your code. The following code shows how I converted the previous three examples into unit tests.\n",
    "\n",
    "Most software architects suggest you should not use an LLM to generate the unit tests because you must know what is being tested."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "id": "PFT-XSZ-l5U7"
   },
   "outputs": [],
   "source": [
    "import unittest\n",
    "import numpy as np\n",
    "import cv2\n",
    "\n",
    "class TestSafeClip(unittest.TestCase):\n",
    "\n",
    "    def setUp(self):\n",
    "        self.image = np.zeros((100, 100, 3), dtype=np.uint8)\n",
    "        cv2.rectangle(self.image, (30, 30), (70, 70), (255, 255, 255), -1)\n",
    "        for i in range(100):\n",
    "            self.image[:, i] = i * 2.55  # Gradient from black to white\n",
    "        self.background_color = (0, 0, 255)  # Blue background\n",
    "\n",
    "    def test_clip_within_bounds(self):\n",
    "        clipped_image, x_off, y_off = safe_clip(self.image, 20, 20, 60, 60, self.background_color)\n",
    "        self.assertEqual(clipped_image.shape, (60, 60, 3))\n",
    "        self.assertEqual((x_off, y_off), (0, 0))\n",
    "        self.assertTrue((clipped_image[0, 0] != self.background_color).all())\n",
    "\n",
    "    def test_clip_at_edge(self):\n",
    "        clipped_image, x_off, y_off = safe_clip(self.image, 50, 50, 100, 100, self.background_color)\n",
    "        self.assertEqual(clipped_image.shape, (100, 100, 3))\n",
    "        self.assertEqual((x_off, y_off), (0, 0))\n",
    "        self.assertTrue((clipped_image[0, 0] == [127, 127, 127]).all())\n",
    "\n",
    "    def test_clip_outside_bounds(self):\n",
    "        clipped_image, x_off, y_off = safe_clip(self.image, -10, -10, 120, 120, self.background_color)\n",
    "        self.assertEqual(clipped_image.shape, (120, 120, 3))\n",
    "        self.assertEqual((x_off, y_off), (10, 10))\n",
    "        self.assertTrue((clipped_image[10, 10] == [0, 0, 0]).all())\n",
    "\n",
    "test = TestSafeClip()\n",
    "test.setUp()\n",
    "test.test_clip_within_bounds()\n",
    "test.test_clip_at_edge()\n",
    "test.test_clip_outside_bounds()"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3.11 (genai)",
   "language": "python",
   "name": "genai"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.8"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
